package com.gitee.sop.message.util.scheduler;


import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 依赖于Quartz 框架
 * <dependency>
 * <groupId>org.quartz-scheduler</groupId>
 * <artifactId>quartz</artifactId>
 * <version>2.3.0</version>
 * </dependency>
 *
 * @author: huwei
 * @date: 2020/3/30 9:29
 * @version: 1.0.0
 */
public class SchedulerUtil {
    private static final Set<TriggerKey> CLEANER_TRIGGER_KEY = new HashSet<>();
    /**
     * 任务调度器，空闲时间；即当没有任务后的等待时间；默认1小时后，关闭任务调度器
     */
    private static final long SCHEDULER_FREE_TIME = TimeUnit.MINUTES.toMillis(10);

    public static JobKey schedule(NoCallExecuteFunction task) throws SchedulerException {
        return schedule(task, 0, 0, 1);
    }

    public static JobKey schedule(NoCallExecuteFunction task, long delayStartMillis, int cycleSeconds, int repeatCount) throws SchedulerException {
        JobDetail jobDetail = JobUtil.newBuilder().withKey(randomJobKey()).withJob(task).build();
        return schedule(jobDetail, simpleTrigger(jobDetail, delayStartMillis, cycleSeconds, repeatCount));
    }

    public static <T> JobKey schedule(NoCallParameterExecuteFunction<T> task, T arg) throws SchedulerException {
        return schedule(task, arg, 0, 0, 1);
    }

    public static <T> JobKey schedule(NoCallParameterExecuteFunction<T> task, T arg, long delayStartMillis, int cycleSeconds, int repeatCount) throws SchedulerException {
        JobDetail jobDetail = JobUtil.newBuilder().withKey(randomJobKey()).withJob(task, arg).build();
        return schedule(jobDetail, simpleTrigger(jobDetail, delayStartMillis, cycleSeconds, repeatCount));
    }

    public static <R> JobKey schedule(ExecuteFunction<R> task, CallFunction<R> call) throws SchedulerException {
        return schedule(task, call, 0, 0, 1);
    }

    public static <R> JobKey schedule(ExecuteFunction<R> task, CallFunction<R> call, long delayStartMillis, int cycleSeconds, int repeatCount) throws SchedulerException {
        JobDetail jobDetail = JobUtil.newBuilder().withKey(randomJobKey()).withJob(task, call).build();
        return schedule(jobDetail, simpleTrigger(jobDetail, delayStartMillis, cycleSeconds, repeatCount));
    }

    public static <T, R> JobKey schedule(ParameterExecuteFunction<T, R> task, T arg, CallFunction<R> call) throws SchedulerException {
        return schedule(task, arg, call, 0, 0, 1);
    }

    public static <T, R> JobKey schedule(ParameterExecuteFunction<T, R> task, T arg, CallFunction<R> call, long delayStartMillis, int cycleSeconds, int repeatCount) throws SchedulerException {
        JobDetail jobDetail = JobUtil.newBuilder().withKey(randomJobKey()).withJob(task, arg, call).build();
        return schedule(jobDetail, simpleTrigger(jobDetail, delayStartMillis, cycleSeconds, repeatCount));
    }

    public static JobKey schedule(JobDetail jobDetail, Trigger trigger) throws SchedulerException {
        QuartzSchedulerManager.getOrCreate().scheduleJob(jobDetail, trigger);

        QuartzSchedulerManager.incrementAndGetJobCount();

        return jobDetail.getKey();
    }

    public static void shutdownJob(JobKey jobKey) throws SchedulerException {
        shutdownJob(jobKey, SCHEDULER_FREE_TIME);
    }

    public static void shutdownJob(JobKey jobKey, long exitGraceTimeMillis) throws SchedulerException {
        if (QuartzSchedulerManager.getOrCreate().checkExists(jobKey)) {
            QuartzSchedulerManager.getOrCreate().pauseJob(jobKey);

            addJobCleaner(jobKey, exitGraceTimeMillis);
        }
    }

    public static void shutdownNow() throws SchedulerException {
        QuartzSchedulerManager.getOrCreate().shutdown();
    }

    private static JobKey randomJobKey() {
        return new JobKey(UUID.randomUUID().toString(), UUID.randomUUID().toString());
    }

    private static TriggerKey randomTriggerKey() {
        return new TriggerKey(UUID.randomUUID().toString(), UUID.randomUUID().toString());
    }

    private static Trigger simpleTrigger(JobDetail jobDetail, long delayStartMillis, int cycleSeconds, int repeatCount) {
        TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();
        triggerBuilder.withIdentity(randomTriggerKey());
        if (delayStartMillis > 0) {
            triggerBuilder.startAt(new Date(System.currentTimeMillis() + delayStartMillis));
        } else {
            triggerBuilder.startNow();
        }
        if (repeatCount > 0) {
            triggerBuilder.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(repeatCount, cycleSeconds));
        } else {
            triggerBuilder.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(cycleSeconds));
        }
        triggerBuilder.forJob(jobDetail);
        return triggerBuilder.build();
    }

    /**
     * 添加任务清理任务
     *
     * @param deleteJobKey 任务key
     * @throws SchedulerException
     */
    private static void addJobCleaner(JobKey deleteJobKey, long exitGraceTimeMillis) throws SchedulerException {
        final JobKey innerDeleteJobKey = deleteJobKey;
        JobDetail jobDetail = JobUtil.newBuilder().withKey(randomJobKey()).withJob(() -> {
            QuartzSchedulerManager.getOrCreate().deleteJob(innerDeleteJobKey);
            //任务数量减一
            int jobCount = QuartzSchedulerManager.decrementAndGetJobCount();
            //当任务小于0时，终止任务
            if (jobCount <= 0) {
                QuartzSchedulerManager.reset();
            }
        }).build();
        Trigger trigger = simpleTrigger(jobDetail, exitGraceTimeMillis, 0, 1);
        //记录内容key
        CLEANER_TRIGGER_KEY.add(trigger.getKey());
        QuartzSchedulerManager.getOrCreate().scheduleJob(jobDetail, trigger);
    }

    /**
     * 调度器管理
     */
    private static class QuartzSchedulerManager {
        private static Scheduler instance;
        private static AtomicInteger jobCount;

        private static synchronized Scheduler getOrCreate() throws SchedulerException {
            if (instance == null) {
                instance = StdSchedulerFactory.getDefaultScheduler();
                init();
            }
            return instance;
        }

        private static synchronized int incrementAndGetJobCount() {
            return jobCount.incrementAndGet();
        }

        private static synchronized int decrementAndGetJobCount() {
            return jobCount.decrementAndGet();
        }

        private static int getJobCount() {
            return jobCount.get();
        }

        private static synchronized void reset() throws SchedulerException {
            instance.shutdown();
            instance = null;
            jobCount = null;
        }

        private static void init() throws SchedulerException {
            instance.getListenerManager().addSchedulerListener(new SchedulerDelayShutdownListener());
            instance.start();
            jobCount = new AtomicInteger(0);
        }
    }

    /**
     * 调度器延时关闭监听器，该监听器将监听所有任务完成情况，并删除已完成1小时以上到任务，或关闭调度器
     */
    private static class SchedulerDelayShutdownListener implements SchedulerListener {
        @Override
        public void jobScheduled(Trigger trigger) {
        }

        @Override
        public void jobUnscheduled(TriggerKey triggerKey) {
        }

        @Override
        public void triggerFinalized(Trigger trigger) {
            if (CLEANER_TRIGGER_KEY.contains(trigger.getKey())) {
                //清理任务key，删除
                CLEANER_TRIGGER_KEY.remove(trigger.getKey());
            } else {
                //添加job清理任务
                try {
                    addJobCleaner(QuartzSchedulerManager.getOrCreate().getJobDetail(trigger.getJobKey()).getKey(), SCHEDULER_FREE_TIME);
                } catch (SchedulerException e) {
                }
            }
        }

        @Override
        public void triggerPaused(TriggerKey triggerKey) {
        }

        @Override
        public void triggersPaused(String s) {
        }

        @Override
        public void triggerResumed(TriggerKey triggerKey) {
        }

        @Override
        public void triggersResumed(String s) {
        }

        @Override
        public void jobAdded(JobDetail jobDetail) {
        }

        @Override
        public void jobDeleted(JobKey jobKey) {
        }

        @Override
        public void jobPaused(JobKey jobKey) {
        }

        @Override
        public void jobsPaused(String s) {
        }

        @Override
        public void jobResumed(JobKey jobKey) {
        }

        @Override
        public void jobsResumed(String s) {
        }

        @Override
        public void schedulerError(String s, SchedulerException e) {
        }

        @Override
        public void schedulerInStandbyMode() {
        }

        @Override
        public void schedulerStarted() {
        }

        @Override
        public void schedulerStarting() {
        }

        @Override
        public void schedulerShutdown() {
        }

        @Override
        public void schedulerShuttingdown() {
        }

        @Override
        public void schedulingDataCleared() {
        }
    }
}
